package cn.codingjc.framework.common.util.download.core;

import cn.codingjc.framework.common.util.download.util.FileUtils;
import cn.codingjc.framework.common.util.download.util.HttpUtils;
import com.google.errorprone.annotations.Var;
import lombok.extern.slf4j.Slf4j;

import java.io.*;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

/**
 * @ClassName Downloader
 * @Description 多线程下载器
 * @Author jccoding
 * @Date 2024/2/29 10:58
 * @Version 1.0
 **/

@Slf4j
public class Downloader {

    private static final int THREAD_COUNT = 10;

    private static final String FILE_TEMP_SUFFIX = ".temp";

    private static ExecutorService executor = Executors.newFixedThreadPool(THREAD_COUNT + 1);

    /**
     * 1. 获取文件大小
     * 2. 任务切分
     * 3. 开始下载
     * 4. 文件合并
     * 5. 清理文件
     * @param url
     */
    public void download(String url) throws Exception {
        String fileName = HttpUtils.getHttpFileName(url);
        long localFileSize = FileUtils.getFileContentLength(fileName);
        // 获取网络文件具体大小
        long httpFileContentLength = HttpUtils.getHttpFileContentLength(url);
        if (localFileSize >= httpFileContentLength) {
            log.info("{}已经下载完毕，无需重新下载", fileName);
            return;
        }
        List<Future<Boolean>> futureList = new ArrayList<>();
        if (localFileSize > 0) {
            log.info("开始续传文件");
        } else{
            log.info("开始下载文件");
        }
        log.info("开始下载时间 {}", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss")));
        long startTime = System.currentTimeMillis();
        // 任务切分
        splitDownload(url, futureList);
        // 开始下载
        for (Future<Boolean> future : futureList) {
            future.get();
        }
        log.info("文件下载完毕 {}，本次下载耗时：{}", fileName, (System.currentTimeMillis() - startTime) / 1000 + "s");
        log.info("结束下载时间 {}", LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd hh:mm:ss")));

        //文件合并
        boolean merge = mergeFile(fileName);
        if (merge) {
            clearTemp(fileName);
        }
    }

    private boolean clearTemp(String fileName) {
        for (int i = 0; i < THREAD_COUNT; i++) {
            File file = new File(fileName + FILE_TEMP_SUFFIX + i);
            file.delete();
        }
        log.info("临时文件清理完毕 {}{}0-{}", fileName, FILE_TEMP_SUFFIX, (THREAD_COUNT - 1));
        return true;
    }

    private boolean mergeFile(String fileName) throws IOException {
        byte[] buffer = new byte[1024 * 10];
        int len = -1;
        try (RandomAccessFile oSavedFile = new RandomAccessFile(fileName, "rw")) {
            for (int i = 0; i < THREAD_COUNT; i++) {
                try (BufferedInputStream bis = new BufferedInputStream(
                   new FileInputStream(fileName + FILE_TEMP_SUFFIX + i))) {
                   while ((len = bis.read(buffer)) != -1) {
                       oSavedFile.write(buffer, 0, len);
                   }
                }
            }
            log.info("文件合并完成！");
        } catch (Exception e){
            log.info("File merge error:", e);
            return false;
        }
        return true;
    }

    /**
     * 任务切分
     * @param url
     * @param futureList
     * @throws IOException
     * @throws ExecutionException
     * @throws InterruptedException
     */
    private void splitDownload(String url, List<Future<Boolean>> futureList) throws IOException, ExecutionException, InterruptedException {
        long httpFileContentLength = HttpUtils.getHttpFileContentLength(url);
        // 任务切分
        long size = httpFileContentLength / THREAD_COUNT;
        long lastSize = httpFileContentLength - (size * (THREAD_COUNT - 1));
        for (int i = 0; i < THREAD_COUNT; i++) {
            long start = i * size;
            Long downloadWindow = (i == THREAD_COUNT - 1) ? lastSize : size;
            Long end = start + downloadWindow;
            if (start != 0) {
                start++;
            }
            DownloadThread downloadThread = new DownloadThread(url, start, end, i, httpFileContentLength);
            Future<Boolean> future = executor.submit(downloadThread);
            futureList.add(future);
        }
    }

    public static void main(String[] args) {
        long size = 10 / 3;
        long lastSize = 10 - (10 / 3 * (3 - 1));
        System.out.println(size);
        System.out.println(lastSize);
    }
}
